.. _Контрактное программирование для структур и классов:

Контрактное программирование для структур и классов
===================================================

.. index::
    контрактное программирование
    контракт

Контрактное программирование очень эффективно для уменьшения количества ошибок при кодировании. Мы рассмотрели
две возможности контрактного программирования ранее в главе "Контрактное программирование
(:ref:`Контрактное программирование<Контрактное программирование>`): Блоки in и out обеспечивают контракты на ввод и вывод
функции.
*Примечание: очень важно, чтобы вы учли рекомендации, приведенные в разделе "Блоки in
против принудительных проверок" в этой главе. Примеры в этой главе основаны на
на предположении, что проблемы с согласованностью объектов и параметров вызваны
ошибками программистов. В противном случае следует использовать проверки enforce внутри тел функций.*

В качестве напоминания давайте напишем функцию, которая вычисляет площадь треугольника по
формуле Герона. Вскоре мы перенесем блоки ввода и вывода этой функции в
конструктор структуры.
Чтобы вычисление работало правильно, длина каждой стороны треугольника
должна быть больше нуля. Кроме того, поскольку невозможно получить треугольник
в котором одна из сторон больше суммы двух других, это условие
также должно быть проверено.
Если эти условия выполнены, то площадь треугольника будет
больше нуля. Следующая функция гарантирует, что все эти требования
выполняются:

.. code-block:: d

   private import std.math;

    double triangleArea(double a, double b, double c)
    in
    {
        // Every side must be greated than zero
        assert(a > 0);
        assert(b > 0);
        assert(c > 0);
        // Every side must be less than the sum of the other two
        assert(a < (b + c));
        assert(b < (a + c));
        assert(c < (a + b));
    }
    out (result)
    {
        assert(result > 0);
    }
    do
    {
        immutable halfPerimeter = (a + b + c) / 2;
        return sqrt(halfPerimeter
                * (halfPerimeter - a)
                * (halfPerimeter - b)
                * (halfPerimeter - c));
    }


.. index::
    предусловие
    постусловие
    in
    out

Предусловия и постусловия для функций-членов
--------------------------------------------

Блоки in и out можно использовать и с функциями-членами.
Давайте преобразуем приведенную выше функцию в функцию-член структуры Triangle:

.. code-block:: d

    import std.stdio;
    import std.math;

    struct Triangle
    {
    private:
        double a;
        double b;
        double c;
    public:
        double area() const
        out (result)
        {
            assert(result > 0);
        }
        do
        {
            immutable halfPerimeter = (a + b + c) / 2;
            return sqrt(halfPerimeter
                    * (halfPerimeter - a)
                    * (halfPerimeter - b)
                    * (halfPerimeter - c));
        }
    }

    void main()
    {
        auto threeFourFive = Triangle(3, 4, 5);
        writeln(threeFourFive.area);
    }


Поскольку стороны треугольника теперь являются переменными-членами, функция больше не принимает
параметров. Поэтому у этой функции нет блока in.
Вместо этого она предполагает, что члены уже имеют согласованные значения.
Согласованность объектов может быть обеспечена следующими возможностями.

Предусловия и постусловия для согласованности объектов
------------------------------------------------------

Приведенная выше функция-член написана в предположении, что члены
объекта уже имеют согласованные значения. Один из способов обеспечить это предположение -
определить блок in для конструктора, чтобы объекты гарантированно
начинают свою жизнь в согласованных состояниях:

.. code-block:: d

    struct Triangle
    {
        // ...
        this(double a, double b, double c)
        in
        {
            // Every side must be greated than zero
            assert(a > 0);
            assert(b > 0);
            assert(c > 0);
            // Every side must be less than the sum of the other two
            assert(a < (b + c));
            assert(b < (a + c));
            assert(c < (a + b));
        }
        do
        {
            this.a = a;
            this.b = b;
            this.c = c;
        }
        // ...
    }


Это предотвращает создание недействительных объектов Triangle во время выполнения:

.. code-block:: d

    auto negativeSide = Triangle(-1, 1, 1);
    auto sideTooLong = Triangle(1, 1, 10);

Блок in конструктора предотвратит появление таких недействительных объектов:

.. code-block:: console

    core.exception.AssertError@deneme.d: Assertion fsilure

Хотя блок out не был определен для приведенного выше конструктора, его можно
можно определить его, чтобы обеспечить согласованность членов сразу после
конструирования.

.. index::
    invariant


invariant() блоки для согласованности объекта
----------------------------------------------

Блоки ввода и вывода конструкторов гарантируют, что объекты начинают свою жизнь
в согласованных состояниях, а блоки входа и выхода функций-членов гарантируют.
что сами эти функции работают правильно.
Однако эти проверки не подходят для того, чтобы гарантировать, что объекты
всегда находятся в согласованных состояниях. Повторять блоки выхода для каждой функции-члена
было бы чрезмерно и чревато ошибками.
Условия, определяющие согласованность и валидность объекта, называются
инвариантами этого объекта. Например, если существует соответствие один к одному
между заказами и счетами класса клиентов, то инвариантом этого
класса будет то, что длина массивов заказов и счетов будет одинаковой.
Если это условие не выполняется для какого-либо объекта, то объект будет находиться в
противоречивое состояние.
В качестве примера инварианта рассмотрим класс School из главы
Глава "Инкапсуляция и защита атрибутов" (:ref:`Инкапсуляция и защита атрибутов<глава_60_start>`):

.. code-block:: d

    class School
    {
    private:
        Student[] students;
        size_t femaleCount;
        size_t maleCount;
        // ...
    }

Объекты этого класса являются непротиворечивыми только в том случае, если инвариант, включающий три его члена
членов удовлетворяется. Длина массива студентов должна быть равна сумме студентов женского и мужского пола:

.. code-block:: d

    assert(students.length == (femaleCount + maleCount));

Если это условие когда-либо будет ложным, значит, в реализации этого класса есть ошибка
данного класса.
Блоки invariant() предназначены для гарантирования инвариантов определяемых пользователем типов. Блоки invariant() определяются внутри тела структуры или класса. Они
содержат проверки утверждений, аналогичные блокам in и out:

.. code-block:: d

    class School
    {
    private:
        Student[] students;
        size_t femaleCount;
        size_t maleCount;
        invariant()
        {
            assert(students.length == (femaleCount + maleCount));
        }
        // ...
    }

При необходимости в пользовательском типе может быть более одного блока invariant().
Блоки invariant() выполняются автоматически в следующие моменты времени:
* После выполнения конструктора: Это гарантирует, что каждый объект начинает
свой путь в неизменном состоянии.
* Перед выполнением деструктора: Это гарантирует, что деструктор
будет выполнен на консистентном объекте.
* До и после выполнения публичной функции-члена: Это гарантирует
что функции-члены не нарушают согласованность объектов.
*Примечание: функции export в этом отношении аналогичны функциям public. (Очень
кратко: экспортные функции - это функции, которые экспортируются в динамические библиотечные
интерфейсы.)*
Если проверка assert внутри блока invariant() не удается, выбрасывается ошибка AssertError.
Это гарантирует, что программа не продолжит выполнение с недопустимыми объектами.
Как и в случае с блоками in и out, проверки внутри блоков invariant() могут быть
отключить с помощью опции командной строки -release:

.. code-block:: console

    $ dmd deneme.d -w -release

Наследование  контрактов
-------------------------

Функции-члены интерфейсов и классов также могут иметь блоки in и out. Это
позволяет интерфейсу или классу определять предварительные условия для производных типов.
а также определять постусловия, от которых могут зависеть его пользователи. Производные
типы могут определять дополнительные блоки in и out для переопределения этих функций-членов
функций. Переопределенные блоки in могут ослабить предварительные условия, а переопределенные блоки out
блоки могут давать больше гарантий.
Пользовательский код обычно абстрагируется от производных типов и пишется таким образом.
чтобы удовлетворить предварительным условиям самого верхнего типа в иерархии. Пользовательский код
даже не знает о производных типах. Поскольку пользовательский код пишется для
контрактов интерфейса, было бы недопустимо, чтобы производный тип ставил
более строгие предварительные условия на переопределенную функцию-член. Однако
предусловия производного типа могут быть более разрешительными, чем предусловия
его суперкласса.
При входе в функцию автоматически выполняются блоки от
самого верхнего типа до самого нижнего в иерархии. Если какой-либо блок in завершился успешно
без сбоев, то предварительные условия считаются выполненными.
Аналогично, производные типы могут определять и блоки out. Поскольку постусловия
о гарантиях, которые предоставляет функция, функции-члены производного
типа должны соблюдать и постусловия своих предков. С другой стороны,
она может предоставлять дополнительные гарантии.
При выходе из функции автоматически выполняются блоки out от
от самого верхнего типа к самому нижнему. Считается, что функция
выполнившей свои постусловия только в том случае, если все блоки out завершились успешно.
Следующая искусственная программа демонстрирует эти возможности на интерфейсе
и классе. Класс требует меньше от вызывающих его лиц, предоставляя при этом больше
гарантий:

.. code-block:: d

    interface Iface
    {
        int[] func(int[] a, int[] b)
        in
        {
            writeln("Iface.func.in");
            /* This interface member function requires that the
                * lengths of the two parameters are equal. */
            assert(a.length == b.length);
        }
        out (result)
        {
            writeln("Iface.func.out");
            /* This interface member function guarantees that the
                * result will have even number of elements.
                * (Note that an empty slice is considered to have
                * even number of elements.) */
            assert((result.length % 2) == 0);
        }
    }

    class Class : Iface
    {
        int[] func(int[] a, int[] b)
        in
        {
            writeln("Class.func.in");
            /* This class member function loosens the ancestor's
                * preconditions by allowing parameters with unequal
                * lengths as long as at least one of them is empty. */
            assert((a.length == b.length) ||
                    (a.length == 0) ||
                    (b.length == 0));
        }
        out (resul)
        {
            writeln("Class.func.out");
            /* This class member function provides additional
                    * guarantees: The result will not be empty and that
                    * the first and the last elements will be equal. */
            assert((result.length != 0) &&
                    (result[0] == result[$ - 1]));
        }
        do
        {
            writeln("Class.func.do");
            /* This is just an artificial implementation to
                    * demonstrate how the 'in' and 'out' blocks are
                    * executed. */
            int[] result;
            if (a.length == 0)
            {
                a = b;
            }
            if (b.length == )
            {
                b = a;
            }
            foreach (i; 0 .. a.length)
            {
                result ~= a[i];
                result ~= b[i];
            }
            result[0] = result[$ - 1] = 42;
            return result;
        }
    }

    import std.stdio;

    void main()
    {
        auto c = new Class();
        /* Although the following call fails Iface's precondition,
                * it is accepted because it fulfills Class' precondition. */
        writeln(c.func([1, 2, 3], []));
    }


Блок in Class выполняется только потому, что параметры не удовлетворяют
предварительным условиям Iface:

.. code-block:: console

    Iface.func.in
    Class.func.in ← would not be executed if Iface.func.in succeeded
    Class.func.do
    Iface.func.out
    Class.func.out
    [42, 1, 2, 2, 3, 42]

Резюме
-------


* in и out блоки полезны и в конструкторах. Они гарантируют, что объекты
  конструируются в допустимых состояниях.
* invariant() блоки гарантируют, что объекты остаются в допустимых состояниях на протяжении
  их жизни.
* Производные типы могут определять блоки in для переопределенных функций-членов.
  Предварительные условия производного типа не должны быть более строгими, чем предварительные условия
  его суперклассов. (Обратите внимание, что отказ от определения блока in означает "отсутствие предусловия вообще
  что может не соответствовать намерениям программиста).
* Производные типы могут определять блоки out для переопределенных функций-членов. В
  в дополнение к своим собственным, производная функция-член должна соблюдать
  постусловия своих суперклассов.
